package com.xvyy.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.core.search.*;
import com.xvyy.tingshu.model.search.SuggestIndex;
import com.xvyy.tingshu.vo.search.AlbumInfoIndexVo;


import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.xvyy.tingshu.album.client.AlbumInfoFeignClient;
import com.xvyy.tingshu.common.result.Result;
import com.xvyy.tingshu.model.album.BaseCategory3;
import com.xvyy.tingshu.model.search.AlbumInfoIndex;
import com.xvyy.tingshu.query.search.AlbumIndexQuery;
import com.xvyy.tingshu.search.service.SearchService;
import com.xvyy.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {


    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Override
    @SneakyThrows
    public List<Map<String, Object>> channel(Long caId) {

        // 1. 根据caId查询该分类下7个热度值最高的三级分类对象
        Result<List<BaseCategory3>> baseCategory3List = albumInfoFeignClient.getC1Id7TopBaseCategory3List(caId);
        List<BaseCategory3> baseCategory3ListData = baseCategory3List.getData();
        Assert.notNull(baseCategory3ListData, "远程调7个指定的3级分类对象失败");
        // 2. 将7个三级分类的id拿到转成FiledValue对象
        List<FieldValue> c3FieldValueIds = baseCategory3ListData.stream().map(baseCategory3 -> {
            return FieldValue.of(baseCategory3.getId());
        }).collect(Collectors.toList());

        // 3.将7个三级分类对象的集合转成一个Map
        Map<Long, BaseCategory3> category3Map = baseCategory3ListData.stream().collect(Collectors.toMap(BaseCategory3::getId, v -> v));

        // 4. 查询es
        // 4.1 构建 dsl 语句
        SearchRequest request = buildChanelDsl(c3FieldValueIds);

        // 4.2 发送dsl语句查询请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(request, AlbumInfoIndex.class);

        // 5. 解析数据
        return parseChanelResult(response, category3Map);
    }

    @Override
    @SneakyThrows
    public AlbumSearchResponseVo searchAlbum(AlbumIndexQuery albumIndexQuery) {

        // 1. 构建dsl语句
        SearchRequest request = buildSearchConditionDsl(albumIndexQuery);
        // 2. 发送dsl语句查询请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(request, AlbumInfoIndex.class);
        // 3. 解析数据
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchResult(response);
        // 4. 设置分页信息
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        Long totalSize = albumSearchResponseVo.getTotal();
        Long totalPages = totalSize % pageSize == 0 ? totalSize / pageSize : totalSize / pageSize + 1;
        albumSearchResponseVo.setTotalPages(totalPages); // 总页数
        albumSearchResponseVo.setPageNo(pageNo);// 当前页码
        albumSearchResponseVo.setPageSize(pageSize);
        // 5. 返回结果
        return albumSearchResponseVo;
    }

    @Override
    @SneakyThrows
    public Set<String> autoCompleteSuggest(String content) {
        // 1. 构建dsl语句
        SearchRequest request = buildCompleteSuggestDsl(content);
        // 2. 发送dsl语句查询请求
        SearchResponse<SuggestIndex> response = elasticsearchClient.search(request, SuggestIndex.class);
        // 3. 解析数据
        Set<String> setResult = parseSuggestResult(response);
        // 4. 如果解析的数据没有十条，查询包含关键字的数据
        if (setResult.size() < 10) {
            // 补数据
            SearchResponse<SuggestIndex>suggestResponse = elasticsearchClient.search(sb -> sb
                    .index("suggestinfo")
                    .query(qb -> qb
                            .match(mqb -> mqb
                                    .field("title")
                                    .query(content))), SuggestIndex.class);
            for (Hit<SuggestIndex> hit : suggestResponse.hits().hits()) {
                String title = null;
                if (hit.source() != null) {
                    title = hit.source().getTitle();
                }
                setResult.add(title);
                if (setResult.size() >= 10) {
                    // 补够了10条数据，直接退出循环
                    break;
                }
            }
        }
        // 5. 返回结果
        return setResult;
    }

    /**
     * 解析关键字自动补全的结果
     *
     * @param response
     * @return
     */
    private Set<String> parseSuggestResult(SearchResponse<SuggestIndex> response) {
        Set<String> setResult = new HashSet<>();
        //3对Map<String, Object>
        //suggestionKeyword  suggestionKeywordPinyin  keywordSequence
        Map<String, List<Suggestion<SuggestIndex>>> suggestMap = response.suggest();
        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : suggestMap.entrySet()) {
            List<Suggestion<SuggestIndex>> entryValue = stringListEntry.getValue();
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : entryValue) {
                List<CompletionSuggestOption<SuggestIndex>> suggestOptions = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> suggestOption : suggestOptions) {
                    SuggestIndex suggestIndex = suggestOption.source();
                    String title = null;// 补全的标题内容
                    if (suggestIndex != null) {
                        title = suggestIndex.getTitle();
                    }
                    setResult.add(title);// 不需要高亮，直接取字段值即可
                }
            }
        }
        return setResult;
    }

    /**
     * 构建关键字自动补全的dsl语句
     *
     * @param content
     * @return
     */
    private SearchRequest buildCompleteSuggestDsl(String content) {
        // 1. 构建Suggester对象
        Suggester.Builder suggestBuilder = new Suggester.Builder();
        // .skipDuplicates(true)：用于去重
        Suggester suggester = suggestBuilder.suggesters("suggestionKeyword", fsb -> fsb
                        .prefix(content).completion(csb -> csb.field("keyword").skipDuplicates(true)))
                .suggesters("suggestionKeywordPinyin", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordPinyin").skipDuplicates(true)))
                .suggesters("keywordSequence", fsb -> fsb.prefix(content).completion(csb -> csb.field("keyword").skipDuplicates(true))).build();
        // 2. 构建SearchRequest对象
        SearchRequest.Builder builder = new SearchRequest.Builder();
        SearchRequest searchRequest = builder.index("suggestinfo").suggest(suggester).build();
        log.info("构建关键字自动补全的dsl语句:{}", searchRequest.toString());
        // 3. 返回SearchRequest对象
        return searchRequest;
    }

    /**
     * 构建搜索专辑dsl语句
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest buildSearchConditionDsl(AlbumIndexQuery albumIndexQuery) {

        // 1. 构建SearchRequest对象也就是dsl语句的{}
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();

        // 2. 构建BoolQueryBuilder对象 也就是dsl语句的bool对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        // 3. 判断查询条件
        // 3.1 判断关键字条件
        String keyword = albumIndexQuery.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            // 关键字的条件拼接到dsl语句中
            //albumTitle字段匹配关键字
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumTitle").query(keyword)));
            //albumIntro字段匹配关键字
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumIntro").query(keyword)));
            //announcerName字段匹配关键字
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("announcerName").query(keyword)));
        }
        // 3.2 判断分类条件 c1Id c2Id c3Id
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category3Id").value(category3Id)));
        }

        // 3.3 判断标签信息（属性值 + 属性值id）： ["2:3", "3:6"]
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attrIdAndValueId : attributeList) {
                String[] split = attrIdAndValueId.split(":");
                String attributeId = split[0];// 属性id
                String attributeValueId = split[1];// 属性值id
                //构建NestedBuilder对象
                NestedQuery.Builder nestedBuilder = new NestedQuery.Builder();
                nestedBuilder
                        .path("attributeValueIndexList")
                        .query(qb -> qb
                                .bool(bqb -> bqb
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.attributeId")
                                                        .value(attributeId)))
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.valueId")
                                                        .value(attributeValueId)))));
                //得到 nestedQuery对象
                NestedQuery nestedQuery = nestedBuilder.build();

                boolQueryBuilder.must(qb -> qb.nested(nestedQuery));
            }
        }

        // 3.4 基础查询条件
        BoolQuery boolQuery = boolQueryBuilder.build();
        Query query = boolQuery._toQuery();  // 将bool放到query对象中
        searchRequestBuilder.index("albuminfo").query(query); // 将query对象{}中

        // 3.5 分页条件
        Integer pageNo = albumIndexQuery.getPageNo();// 当前页码
        Integer pageSize = albumIndexQuery.getPageSize();// 每页显示条数
        searchRequestBuilder.from((pageNo - 1) * pageSize).size(pageSize);// 从第几条数据开始，每页显示多少条

        // 3.6 排序条件 默认按照热度值排序 ， 如果
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] split = order.split(":");
            String dimension = split[0]; // 排序维度
            String rule = split[1]; // 排序规则
            SortOrder realRule = rule.equals("asc") ? SortOrder.Asc : SortOrder.Desc;// 真实排序规则
            // 判断是哪个排序维度
            switch (dimension) {
                case "1":
                    // 根据热度值排序
                    searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("hotScore").order(realRule)));
                    break;
                case "2":
                    // 根据播放量排序
                    searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("playStatNum").order(realRule)));
                    break;
                case "3":
                    // 根据创建时间排序
                    searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("createTime").order(realRule)));
            }
        } else {
            // 默认按照热度值排序
            searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("hotScore").order(SortOrder.Desc)));
        }

        // 3.7 高亮条件
        searchRequestBuilder.highlight(hb -> hb.fields("albumTitle", hfb -> hfb.preTags("<font  style='color:red'>").postTags("</font >")));

        // 4.构建聚合条件
        SearchRequest searchRequest = searchRequestBuilder.build();
        // 5. 打印dsl语句
        log.info("构建搜索专辑的dsl语句:{}", searchRequest.toString());
        // 6. 返回构建好的dsl语句对象
        return searchRequest;
    }

    /**
     * 搜索专辑响应 数据解析
     *
     * @param response
     * @return
     */
    private AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> response) {

        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();

        TotalHits totalHits = response.hits().total();
        long total = totalHits != null ? totalHits.value() : 0;
        ArrayList<AlbumInfoIndexVo> albumInfoIndexVoList = new ArrayList<>();
        response.hits().hits().forEach(hit -> {
            AlbumInfoIndex albumInfoIndex = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            if (albumInfoIndex != null) {
                BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            }
            // 高亮数据
            Map<String, List<String>> highlight = hit.highlight();
            List<String> albumTitle = highlight.get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitle)) {
                String highlightAlbumTitle = albumTitle.get(0);
                albumInfoIndexVo.setAlbumTitle(highlightAlbumTitle);
            }
            albumInfoIndexVoList.add(albumInfoIndexVo);
        });

        albumSearchResponseVo.setList(albumInfoIndexVoList);
        albumSearchResponseVo.setTotal(total);

        return albumSearchResponseVo;
    }

    /**
     * 解析数据
     * Map:三级分类对象
     *
     * @param response
     * @param category3Map
     * @return
     */
    private List<Map<String, Object>> parseChanelResult(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> category3Map) {

        ArrayList<Map<String, Object>> result = new ArrayList<>();

        // 1. 根据response对象获取aggregations对象
        Aggregate category3IdAggs = response.aggregations().get("category3IdAggs");
        // 2. 根据aggregations对象转成LongAggregate的聚合类型
        LongTermsAggregate lterms = category3IdAggs.lterms();
        // 3. 根据LongTermsAggregate对象从lterms中获取buckets
        List<LongTermsBucket> buckets = lterms.buckets().array();
        buckets.forEach(bucket -> {
            // map是每一个三级分类对象 kv kv
            long c3Id = bucket.key();
            HashMap<String, Object> map = new HashMap<>();
            map.put("baseCategory3", category3Map.get(c3Id)); // baseCategory3 三级分类对象
            Aggregate albumScoreTopAgg = bucket.aggregations().get("albumScoreTopAgg");
            TopHitsAggregate topHitsAggregate = albumScoreTopAgg.topHits();
            List<Hit<JsonData>> hits = topHitsAggregate.hits().hits();
            ArrayList<AlbumInfoIndex> albumInfoIndexList = new ArrayList<>();
            hits.forEach(hit -> {
                // hit是每一个热度值高的专辑对象
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
                albumInfoIndexList.add(albumInfoIndex);
            });
            map.put("list", albumInfoIndexList);  //list这个key是这个三级分类下的6个热度值高的专辑集合
            result.add(map);
        });


        // 4.返回结果
        return result;
    }


    /**
     * 首页数据的展示的dsl语句对象
     *
     * @param c3FieldValueIds
     * @return
     */
    private SearchRequest buildChanelDsl(List<FieldValue> c3FieldValueIds) {

        SearchRequest request = SearchRequest.of(builder -> builder
                .index("albuminfo")
                .query(qb -> qb
                        .terms(tqb -> tqb
                                .field("category3Id")
                                .terms(tfb -> tfb
                                        .value(c3FieldValueIds))))
                .aggregations("category3IdAggs", ab -> ab
                        .terms(tab -> tab
                                .field("category3Id")
                                .size(7))
                        .aggregations("albumScoreTopAgg", sab -> sab
                                .topHits(tab -> tab
                                        .sort(sob -> sob
                                                .field(fsb -> fsb
                                                        .field("hotScore")
                                                        .order(SortOrder.Desc)))
                                        .size(6)))));
        log.info("构建的dsl语句:{}", request);
        return request;
    }
}
